package lsp12

import (
    "errors"
    "time"
    "encoding/json"
	"P1-f12/official/lsplog"
    "P1-f12/official/lspnet"
    "P1-f12/contrib/dserver"
    )

// iLspClient -- the struct each LspClient maintains:
// TODO
// should include at least:
// 1. address of the servere
// 2. UDP connection to the server
// 3. ConnID
// 4. the buffer written by the server
// 5. the LSP parameter
//
type iLspClient struct {
    // address of the server
    serverAddr *lspnet.UDPAddr
    // UDP connection to the server
    serverConn *lspnet.UDPConn
    // Connection ID
    connId uint16
    // buffer list of the received packets
    rBuf *dserver.Buf
    // buffer list of the packets to send
    sBuf *dserver.Buf
    // LSP parameters
    params *LspParams
    // sequence number of received packets
    rSeqNum uint8
    // sequence number of sent packets
    sSeqNum uint8
    // indicate whether the Ack is received -- i.e. new DATA packet arrived
    rAckedChan chan int
    // indicate whether Ack of the preivous sent packet is received
    sAckedChan chan int
    // timer to be fired every epoch interal
    epochTicker *time.Ticker
    // activated when time ticker fired
    epochChan chan time.Time 
    // indicate the client calles close()
    bClose bool
}

type BufNode struct {
    Type byte
    Payload []byte
}


// iNewLspClient -- setup and initiate the activities of an LSP client
// parameter: 
//     hostport -- the domain name and port number of the server
//     params   -- pointer to a struct giving the LSP parameters,
//                set it nil as default values
// return value: (returns after connection established, i.e. Ack msg received
//     LspClient -- the pointer to the established Client struct 
//     error     -- non-nil if connection cannot be established
//
func iNewLspClient(hostport string, params *LspParams) (*LspClient, error) {
    client := new(LspClient)
    saddr, err := lspnet.ResolveUDPAddr("udp", hostport)
    if lsplog.CheckReport(1, err){
        lsplog.Vlogf(1, "Error: in iNewLspClient, wrong server hostport fmt - %s\n", hostport)
        return nil, err
    }
    lsplog.Vlogf(1, "in iNewLspClient, server addr resloved: %s\n", saddr)

    srvudp, err := lspnet.DialUDP("udp", nil, saddr)
    if lsplog.CheckReport(1, err){
        lsplog.Vlogf(1, "Error: in iNewLspClient, DialUDP to server addr %s fail\n", saddr)
        return nil, err
    }
    lsplog.Vlogf(1, "in iNewLspClient, UDPConnection established\n")

    // init the params info
    client.params.EpochLimit =  params.EpochLimit
    if client.params.EpochLimit == 0 {
        client.params.EpochLimit = 5
    }
    client.params.EpochMilliseconds = params.EpochMilliseconds
    if client.params.EpochMilliseconds == 0 {
        client.params.EpochMilliseconds = 2000
    }
    // init some values in client 
    client.serverAddr = saddr
    client.serverConn = srvudp
    client.rBuf = dserver.NewBuf()
    client.sBuf = dserver.NewBuf()
    client.rSeqNum = 0
    client.sSeqNum = 0
    client.bClose = false
    client.rAckedChan = make(chan int)
    client.sAckedChan = make(chan int)
    client.epochChan = make(chan time.Time)

    //cli := LspClient{client}
    lsplog.Vlogf(1, "in iNewLspClient, run iEpochHandler\n");
    go iEpochHandler(client)
    lsplog.Vlogf(1, "in iNewLspClient, run iNetworkHandler\n");
    go iNetworkHandler(client)
    lsplog.Vlogf(1, "in iNewLspClient, run iApplicationHandler\n");
    go iApplicationHandler(client)

    //send the Connect to the server
    connBufNode := new(BufNode)
    connBufNode.Type = MsgCONNECT
    connBufNode.Payload = nil
    client.sBuf.Insert(connBufNode)
    lsplog.Vlogf(1, "in iNewLspClient, sending connect request to server\n");

    time.Sleep(time.Duration(client.params.EpochMilliseconds)*time.Millisecond)
    //receive the Connect Ack from server 

    if client.rBuf.Empty() == true {
        err := errors.New("connection failed")
        lsplog.Vlogf(1, "Error: in iNewLspClient, connection can't be established: no connection ACK received!\n");
        return nil, err
    }else {
        connAckBufNode := client.rBuf.Front().(BufNode)
        client.rBuf.Remove()
        if connAckBufNode.Type != MsgACK {
            lsplog.Vlogf(1, "erro : in iNewLspClient, connect ack recevied but wrong format!!\n");
            err := errors.New("wrong connect ack format")
            return nil, err
        }else {
            client.connId = uint16(b2i(connAckBufNode.Payload))
            lsplog.Vlogf(1, "in iNewLspClient, connect ack received from server\n");
        //cli := LspClient(client)
            return client, nil
        }
    }

	return nil, lsplog.NotImplemented("iNewLspClient")
}

// Return the Connection ID for a client
func (cli *LspClient) iConnId() uint16 {
	return cli.iLspClient.connId
}

// Client Read. Return non-nil error when connection lost
// Call blocks until value available to read, or network disconnected
func (cli *LspClient) iRead() ([]byte, error) {
	return nil, lsplog.NotImplemented("iRead")
}

// Client write. Return non-nil error when connection lost
// Return immediately
func (cli *LspClient) iWrite(payload []byte) error {
	return lsplog.NotImplemented("iWrite")
}

// Signal to server that no further interations from client are required.
// Call blocks unitl all pending message to server have been sent, 
//              or network connection lost
// Calling close on already closed connection should be possible.
// Application should not attempt call to Read or Write after calling Close
// Once the call to Close has been completed, any goroutines associated with the clinet should exit
func (cli *LspClient) iClose() {

    //remember to close epochTicker
	return
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// in each epoch:
// client do the following three things:
// 1. resend a conn request, if the original conn request has not yet been acked
// 2. send an ack msg for the most recently received data msg,
//     or an ack with seq number 0 if no data msg have been received
// 3. if a data msg has been sent, but not yet acked, then resend the data msg

// how to protect Buf to be thread-safe 
// use defer client/server mode implemented in dserver.go

// Read packets from the UDP connection
func iNetworkHandler(client *LspClient) {
    var buffer [1500]byte
    var msg LspMessage
    var newNode BufNode
    for {
        if client.bClose == true {
            //TODO: deal with more details about close()
            lsplog.Vlogf(6, "in iNetworkHandler : close() detected, exit this handler\n");
            break
        }else {
            n, cliaddr, err := client.serverConn.ReadFromUDP(buffer[0:])
            if lsplog.CheckReport(1, err){
                lsplog.Vlogf(6, "Error: in iNetworkHandler, ReadFromUDP Error: error=%v\n", err)
                continue
            }
            lsplog.Vlogf(4, "in iNetworkHandler : new packet read from UDP: %v bytes read, cliaddr= %v, content=%v\n", n, cliaddr, buffer)
            msg = b2msg(buffer[0:])
            if msg.Type == MsgACK {
                if msg.SeqNum == 0 {
                    //the connect ack
                    newNode.Type = MsgACK
                    newNode.Payload = i2b(int(msg.ConnId))
                    client.rBuf.Insert(newNode)
                    lsplog.Vlogf(6, "in iNetworkHandler : insert connection ack to rBuf\n");
                }else {
                    if msg.SeqNum == client.sSeqNum {
                        //valid ack, active the sAckedChan to unblock ApplicationHandler
                        lsplog.Vlogf(6, "in iNetworkHandler : activate sAckedChan -- the ACK arrived\n");
                        <-client.sAckedChan
                    }else {
                        //invalid ack -- seqNum not matched!
                        lsplog.Vlogf(6, "in iNetworkHandler : discard invalid ack -- sSeqNum %v not match current seqNum %v!\n", msg.SeqNum, client.sSeqNum);
                    }
                }
            }else {
                if msg.Type == MsgDATA {
                    if msg.SeqNum == client.sSeqNum {
                        // valid DATA packet : insert the newNode to rBuf
                        // unblock ApplicationHandler to send ACK to this DATA packet
                        lsplog.Vlogf(6, "in iNetworkHandler : valid DATA packet, sSeqNum = %v\n", msg.SeqNum);
                        lsplog.Vlogf(6, "in iNetworkHandler : activate rAckedChan -- new DATA packet arrived\n");
                        <-client.rAckedChan

                        newNode.Type = MsgDATA
                        newNode.Payload = msg.Payload
                        client.rBuf.Insert(newNode)
                        lsplog.Vlogf(6, "in iNetworkHandler : insert DATA payload to rBuf\n");

                    }else {
                        //invalid DATA packet -- seqNum not matched
                        lsplog.Vlogf(6, "in iNetworkHandler : invalid ack -- sSeqNum %v not match current seqNum %v!\n", msg.SeqNum, client.sSeqNum);
                    }
                }else {
                    //invalid msg.Type -- not MsgACK nor MsgDATA
                    lsplog.Vlogf(6, "in iNetworkHandler : invalid packetType : %v!\n", msg.Type);
                }
            }
        }

    }

}

// Handles write and close requests from the application
func iApplicationHandler(client *LspClient) {
}

// Activates every time the epoch timer fires
func iEpochHandler(client *LspClient) {
    lsplog.Vlogf(5, "---epochHandler begins---\n") 
    client.epochTicker = time.NewTicker(time.Duration(client.params.EpochMilliseconds)*time.Millisecond)
    lsplog.Vlogf(5, "in iEpochHandler : epochTicker established\n") 
    //client.epochChan = client.epochTicker.C
}

// Convert integer to byte array         
func i2b(i int) []byte {
    b, _ := json.Marshal(i)
    return b
}

// Convert byte array back to integer
func b2i(b []byte) int {
    var i int
    json.Unmarshal(b, &i)
    return i
}

// Convert LspMessage struct to byte array         
func msg2b(msg LspMessage) []byte {
    b, _ := json.Marshal(msg)
    return b
}

// Convert byte array back to LspMessage struct
func b2msg(b []byte) LspMessage {
    var msg LspMessage
    json.Unmarshal(b, &msg)
    return msg
}
